"""
Copyright © 2023 Walkline Wang (https://walkline.wang)
Gitee: https://gitee.com/walkline/micropython-ws2812-led-clock
"""
from utime import sleep_ms
from micropython import const
from machine import SoftI2C, I2C, Pin


#region registers
REG_WHO_AM_I = const(0x0f)

REG_CTRL_REG0 = const(0x1e)
REG_CTRL_REG1 = const(0x20)
REG_CTRL_REG2 = const(0x21)
REG_CTRL_REG3 = const(0x22)
REG_CTRL_REG4 = const(0x23)
REG_CTRL_REG5 = const(0x24)
REG_CTRL_REG6 = const(0x25)

REG_INT1_CFG = const(0x30)
REG_INT1_SRC = const(0x31)
REG_INT1_THS = const(0x32)
REG_INT1_DUR = const(0x33)

REG_INT2_CFG = const(0x34)
REG_INT2_SRC = const(0x35)
REG_INT2_THS = const(0x36)
REG_INT2_DUR = const(0x37)

REG_CLICK_CFG = const(0x38)
REG_CLICK_SRC = const(0x39)
REG_CLICK_THS = const(0x3a)

REG_TIME_LIMIT   = const(0x3b)
REG_TIME_LATENCY = const(0x3c)
REG_TIME_WINDOW  = const(0x3d)

REG_TEMP_CFG_REG  = const(0x1f)
REG_REFERENCE     = const(0x26)
REG_FIFO_CTRL_REG = const(0x2e)
#endregion registers


#region register bits
# == CTRL_REG1 ==
BIT_ODR0 = 4
BIT_LPEN = 3

# == CTRL_REG2 ==
BIT_HP_IA1 = 0

# == CTRL_REG3 ==
BIT_I1_CLICK = 7
BIT_I1_IA1   = 6
BIT_I1_IA2   = 5

# == CTRL_REG4 ==
BIT_BDU = 7 # Block Data Update
BIT_FS0 = 4 # Full-scale configuration
BIT_HR  = 3 # Operating mode selection

# == CTRL_REG5 ==
BIT_BOOT = 7 # Reboot memory content

# == CTRL_REG6 ==
BIT_I2_CLICK = 7
BIT_I2_IA1   = 6
BIT_I2_IA2   = 5

# == CLICK_THS ==
BIT_THS0 = 0

# == TIME_LIMIT ==
BIT_TLI0 = 0

# == INT1_CFG ==
BIT_ZHIE = 5
BIT_ZLIE = 4
BIT_YHIE = 3
BIT_YLIE = 2
BIT_XHIE = 1
BIT_XLIE = 0

# == INT1_DURATION ==
BIT_D0 = 0
#endregion register bits


class LIS2DH12LiteException(BaseException):
	pass


class LIS2DH12Lite(object):
	#region constants
	PROPERTY_DISABLED = 0
	PROPERTY_ENABLED  = 1

	ID = const(0x33) # (Who am I)
	IIC_ADDRS = (
		0x18, # 001 1000 | connected to gnd
		0x19  # 001 1001 | connected to vcc
	)

	# Full scale
	FS_2G  = 0b00
	FS_4G  = 0b01
	FS_8G  = 0b10
	FS_16G = 0b11

	# Operating mode
	OPM_NORMAL_10BIT   = 0b00
	OPM_HIGH_RES_12BIT = 0b01
	OPM_LOW_POWER_8BIT = 0b10

	# Output data rate
	ODR_POWER_DOWN          = 0B0000
	ODR_1HZ                 = 0B0001
	ODR_10HZ                = 0B0010
	ODR_25HZ                = 0B0011
	ODR_50HZ                = 0B0100
	ODR_100HZ               = 0B0101
	ODR_200HZ               = 0B0110
	ODR_400HZ               = 0B0111
	ODR_1620HZ_LP           = 0B1000
	ODR_5376HZ_LP_1344HZ_NM = 0B1001
	ODR_RANGES              = range(ODR_POWER_DOWN, ODR_5376HZ_LP_1344HZ_NM + 1)

	# CLICK_CFG
	CCFG_DISABLE_CLICK = 0b000000
	CCFG_SINGLE_CLICK  = 0b010101
	CCFG_DOUBLE_CLICK  = 0b101010

	# INTx_CFG
	ICFG_OR_COMBINATION  = 0b00
	ICFG_AND_COMBINATION = 0b10
	ICFG_6D_MOVEMENT     = 0b01
	ICFG_6D_DIRECTION    = 0b11

	# CTRL_REG2, High-pass filter
	HPF_DISC_FROM_INT_GENERATOR = 0b000
	HPF_ON_INT1_GEN             = 0b001
	HPF_ON_INT2_GEN             = 0b010
	HPF_ON_TAP_GEN              = 0b100
	HPF_ON_INT1_INT2_GEN        = 0b011
	HPF_ON_INT1_TAP_GEN         = 0b101
	HPF_ON_INT2_TAP_GEN         = 0b110
	HPF_ON_INT1_INT2_TAP_GEN    = 0b111

	KIND_INT1 = 0
	KING_INT2 = 1
	#endregion constants

	def __init__(self, i2c:SoftI2C=None, i2c_addr=IIC_ADDRS[1]):
		assert isinstance(i2c, (SoftI2C, I2C)), LIS2DH12LiteException('i2c is not I2C instance')
		assert i2c_addr in LIS2DH12Lite.IIC_ADDRS, LIS2DH12LiteException('invalid i2c address')

		self.__i2c = i2c
		self.__i2c_addr = i2c_addr

	def device_id(self):
		'''获取芯片 ID'''
		return self.__read_mem(REG_WHO_AM_I)[0]

	def reset(self):
		'''软复位寄存器数据'''
		self.output_data_rate(LIS2DH12Lite.ODR_POWER_DOWN)

		self.__write_mem(REG_CTRL_REG0,         0x10)
		self.__write_mem(REG_CTRL_REG1,         0x07)
		self.__write_mem(REG_CTRL_REG2, bytearray(5))
		self.__write_mem(REG_TEMP_CFG_REG,      0x00)
		self.__write_mem(REG_REFERENCE,         0x00)
		self.__write_mem(REG_FIFO_CTRL_REG,     0x00)
		self.__write_mem(REG_INT1_CFG,          0x00)
		self.__write_mem(REG_INT1_THS,  bytearray(3))
		self.__write_mem(REG_INT2_THS,  bytearray(3))
		self.__write_mem(REG_CLICK_THS, bytearray(6))

		data = self.__read_mem(REG_CTRL_REG5)[0]
		self.__write_mem(REG_CTRL_REG5, self.__set_bit(data, BIT_BOOT))
		sleep_ms(5)

	def block_data_update_enabled(self, enabled:bool):
		'''
		启用/禁用块数据更新功能
		- 启用：数据块读取后更新
		- 禁用：数据块持续更新
		'''
		data = self.__set_mode(self.__read_mem(REG_CTRL_REG4)[0], BIT_BDU, enabled)
		self.__write_mem(REG_CTRL_REG4, data)

	def full_scale(self, scale:int):
		'''设置满量程数值'''
		data = self.__set_bits(self.__read_mem(REG_CTRL_REG4)[0], BIT_FS0, scale, 2)
		self.__write_mem(REG_CTRL_REG4, data)

	def operating_mode(self, mode:int):
		'''设置操作模式，包括高分辨率、正常和低功耗模式'''
		data1 = self.__read_mem(REG_CTRL_REG1)[0]
		data2 = self.__read_mem(REG_CTRL_REG4)[0]

		if mode == LIS2DH12Lite.OPM_HIGH_RES_12BIT:
			self.__cls_bit(data1, BIT_LPEN)
			self.__set_bit(data2, BIT_HR)
		elif mode == LIS2DH12Lite.OPM_NORMAL_10BIT:
			self.__cls_bit(data1, BIT_LPEN)
			self.__cls_bit(data2, BIT_HR)
		elif mode == LIS2DH12Lite.OPM_LOW_POWER_8BIT:
			self.__set_bit(data1, BIT_LPEN)
			self.__cls_bit(data2, BIT_HR)

		self.__write_mem(REG_CTRL_REG1, data1)
		self.__write_mem(REG_CTRL_REG4, data2)

	def output_data_rate(self, power_mode:int):
		'''设置输出数据时钟频率'''
		if power_mode not in LIS2DH12Lite.ODR_RANGES:
			power_mode = LIS2DH12Lite.ODR_POWER_DOWN

		data = self.__set_bits(self.__read_mem(REG_CTRL_REG1)[0], BIT_ODR0, power_mode, 4)
		self.__write_mem(REG_CTRL_REG1, data)

	def click_config(self, mode:int):
		'''点击中断内容设置，可对三轴分别设置单击、双击中断'''
		self.__write_mem(REG_CLICK_CFG, mode)

	def click_source(self):
		'''
		获取点击中断寄存器中的数据
		@return: [x, y, z, sign, sclick, dclick, ia]
		'''
		data = self.__read_mem(REG_CLICK_SRC)[0]
		return tuple(reversed([int(i) for i in f'{data:08b}'][1:]))

	def high_pass_config(self, mode:int):
		'''高通滤波器内容设置（只能设置点击和 6D 识别状态）'''
		data = self.__set_bits(self.__read_mem(REG_CTRL_REG2)[0], BIT_HP_IA1, mode, 3) # hp_ia1, hp_ia2, hp_click
		self.__write_mem(REG_CTRL_REG2, data)

	def int_config(self, kind:int, *, mode=ICFG_OR_COMBINATION, zhie=0, zlie=0, yhie=0, ylie=0, xhie=0, xlie=0):
		'''中断内容设置，包括 6D 识别和三轴触发中断设置'''
		data = LIS2DH12Lite.PROPERTY_DISABLED
		reg  = REG_INT1_CFG if kind == LIS2DH12Lite.KIND_INT1 else REG_INT2_CFG

		data = self.__set_bits(data, 6, mode, 2)
		data = self.__set_mode(data, BIT_ZHIE, zhie)
		data = self.__set_mode(data, BIT_ZLIE, zlie)
		data = self.__set_mode(data, BIT_YHIE, yhie)
		data = self.__set_mode(data, BIT_YLIE, ylie)
		data = self.__set_mode(data, BIT_XHIE, xhie)
		data = self.__set_mode(data, BIT_XLIE, xlie)

		self.__write_mem(reg, data)

	def int_source(self, kind:int):
		'''获取中断寄存器中的数据'''
		reg  = REG_INT1_SRC if kind == LIS2DH12Lite.KIND_INT1 else REG_INT2_SRC
		data = self.__read_mem(reg)[0]
		return data

	def click_threshold(self, value:int):
		data = self.__set_bits(self.__read_mem(REG_CLICK_THS)[0], BIT_THS0, value, 7) # ths0 ~ ths6
		self.__write_mem(REG_CLICK_THS, data)

	def click_time_limit(self, value:int):
		data = self.__set_bits(self.__read_mem(REG_TIME_LIMIT)[0], BIT_TLI0, value, 7) # tli0 ~ tli6
		self.__write_mem(REG_TIME_LIMIT, data)

	def click_time_latency(self, value:int):
		self.__write_mem(REG_TIME_LATENCY, value)

	def click_time_window(self, value:int):
		self.__write_mem(REG_TIME_WINDOW, value)

	def int_enabled(self, kind:int, *, click=False, ia1=False, ia2=False):
		'''启用/禁用中断，包括点击中断和事件中断'''
		reg     = REG_CTRL_REG3 if kind == LIS2DH12Lite.KIND_INT1 else REG_CTRL_REG6
		i_click = BIT_I1_CLICK  if kind == LIS2DH12Lite.KIND_INT1 else BIT_I2_CLICK
		i_ia1   = BIT_I1_IA1    if kind == LIS2DH12Lite.KIND_INT1 else BIT_I2_IA1
		i_ia2   = BIT_I1_IA2    if kind == LIS2DH12Lite.KIND_INT1 else BIT_I2_IA2

		data = self.__read_mem(reg)[0]
		data = self.__set_mode(data, i_click, click)
		data = self.__set_mode(data, i_ia1, ia1)
		data = self.__set_mode(data, i_ia2, ia2)

		self.__write_mem(reg, data)

	def int_duration(self, kind:int, value:int):
		'''设置中断持续时间'''
		reg = REG_INT1_DUR if kind == LIS2DH12Lite.KIND_INT1 else REG_INT2_DUR

		data = self.__set_bits(self.__read_mem(reg)[0], BIT_D0, value, 7) # d0 ~ d6
		self.__write_mem(reg, data)

	def int_threshold(self, kind:int, value:int):
		'''设置中断阈值'''
		# 1 LSb = 16 mg @ FS = 2 g, 32 mg @ FS = 4 g, 62 mg @ FS = 8 g, 186 mg @ FS = 16 g
		reg = REG_INT1_THS if kind == LIS2DH12Lite.KIND_INT1 else REG_INT2_THS

		data = self.__set_bits(self.__read_mem(reg)[0], BIT_THS0, value, 7) # ths0 ~ ths6
		self.__write_mem(reg, data)


	#region tools function
	def __set_bit(self, data:int, index:int):
		'''字节指定位置 1'''
		return data | (1 << index)

	def __get_bit(self, data:int, index:int):
		'''获取字节指定位数据'''
		return (data >> index) & 0x1

	def __cls_bit(self, data:int, index:int):
		'''字节指定位置 0'''
		return data & ~(1 << index)

	def __set_bits(self, data:int, start:int, value:int, length:int):
		'''设置字节多位位数据'''
		v1 = value << start
		v2 = data & (0xff >> (8 - start))
		v3 = data >> (start + length) << (start + length)

		return v3 | v2 | v1

	def __get_bits(self, data:int, start:int, length:int):
		'''获取字节多位位数据'''
		return (data >> start) & (0xff >> (8 - length))

	def __set_mode(self, data:int, index:int, mode):
		'''设置字节指定位置 0 或 置 1'''
		return self.__set_bit(data, index) if mode else self.__cls_bit(data, index)

	def __write_mem(self, mem_addr:int, data):
		'''向指定的寄存器写入数据'''
		if isinstance(data, int):
			data = [data]

		self.__i2c.writeto_mem(self.__i2c_addr, mem_addr, bytearray(data))

	def __read_mem(self, mem_addr:int, nbytes=1):
		'''从指定的寄存器读取数据'''
		if nbytes > 1:
			mem_addr |= 0x80

		return bytearray(self.__i2c.readfrom_mem(self.__i2c_addr, mem_addr, nbytes))
	#endregion tools function


	#region dummy
	_ = 0
	#endregion dummy


lis2dh12 = None
count_1 = count_2 = 0

def int1_double_click_callback(pin):
	global lis2dh12, count_1
	count_1 += 1
	label = ('x', 'y', 'z', 'sign', 'sc', 'dc', 'ia')

	print(f'[{count_1}]double clicked\n- {list(zip(label, lis2dh12.click_source()))}')

def int2_orientation_callback(pin):
	global lis2dh12, count_2
	count_2 += 1

	print(f'[{count_2}]rotated: ', end='')
	direction = lis2dh12.int_source(LIS2DH12Lite.KING_INT2) & 0x3f

	if direction == 0x01:
		print(f'direction XL')
	elif direction == 0x02:
		print(f'direction XH')
	elif direction == 0x04:
		print(f'direction YL')
	elif direction == 0x08:
		print(f'direction YH')
	elif direction == 0x10:
		print(f'direction ZL')
	elif direction == 0x20:
		print(f'direction ZH')
	elif direction == 0x00:
		pass
	else:
		print(f'direction {direction}')

def test_double_click_and_orientation(lis2dh12:LIS2DH12Lite):
	lis2dh12.click_threshold(0x18) # ths * (fs/128) = 375mg, @2g
	lis2dh12.click_time_limit(0x0c) # tli * (1/ord) = 60ms, @200hz
	lis2dh12.click_time_latency(0x06) # tla * (1/ord) = 30ms, @200hz
	lis2dh12.click_time_window(0x70) # tw * (1/ord) = 560ms, @200hz
	lis2dh12.click_config(LIS2DH12Lite.CCFG_DOUBLE_CLICK)
	lis2dh12.int_enabled(LIS2DH12Lite.KIND_INT1, click=True)

	int1_pin = Pin(10, Pin.IN, Pin.PULL_UP)
	int1_pin.irq(int1_double_click_callback, trigger=Pin.IRQ_RISING)


	lis2dh12.int_threshold(LIS2DH12Lite.KING_INT2, 0x36) # ths * 16 = 864mg, @2g
	lis2dh12.int_duration(LIS2DH12Lite.KING_INT2, 0x30) # dur * (1/ord) = 240ms, @200hz
	lis2dh12.int_config(LIS2DH12Lite.KING_INT2, mode=LIS2DH12Lite.ICFG_6D_DIRECTION, zhie=1, zlie=1, xhie=1, xlie=1)
	lis2dh12.int_enabled(LIS2DH12Lite.KING_INT2, ia2=True)

	int2_pin = Pin(20, Pin.IN, Pin.PULL_UP)
	int2_pin.irq(int2_orientation_callback, trigger=Pin.IRQ_RISING)


	lis2dh12.high_pass_config(LIS2DH12Lite.HPF_ON_TAP_GEN)
	lis2dh12.full_scale(LIS2DH12Lite.FS_2G)
	lis2dh12.operating_mode(LIS2DH12Lite.OPM_HIGH_RES_12BIT)
	lis2dh12.output_data_rate(LIS2DH12Lite.ODR_200HZ)
	lis2dh12.block_data_update_enabled(True)


if __name__ == '__main__':
	sleep_ms(10) # sensor boot time

	i2c = SoftI2C(scl=Pin(3), sda=Pin(2))
	print(f'scanned iic device: {i2c.scan()}')

	lis2dh12 = LIS2DH12Lite(i2c, 0x19)

	if lis2dh12.device_id() != LIS2DH12Lite.ID:
		print('invalid lis2dh12 device id')
	else:
		lis2dh12.reset()
		test_double_click_and_orientation(lis2dh12)
